<?php

use \Workerman\Worker;
use \Workerman\Connection\AsyncTcpConnection;
use \Workerman\Lib\Timer;


global $is_debug;

function get_config($path = null)
{
    global $is_debug;
    if(is_null($path)){
        $path = __DIR__."/config/config.json";
    }

    

    if(file_exists($path)){
        $content = file_get_contents($path);
        $config = json_decode($content,true);

        if(isset($config['debug']) && $config['debug'] == true){
            $is_debug = true;
        }else{
            $is_debug = false;
        }

        $config = check_config($config);
        $config = check_config_port($config);
        return $config;
    }else{
        throw new \Exception('配置文件不存在');
    }
}


/**
 * check_config_port
 * 检查端口是否重复
 * @param mixed $config 
 * @return mixed 
 */
function check_config_port($config)
{
    
    $config = check_config($config);
    
    if(isset($config['nat_list'])){

        $local_used_host_list = [];
        $server_used_host_list = [];
        
        foreach ($config['nat_list'] as $c_key => $c_value) {
            $local_host = "{$c_value['local_port']}:{$c_value['local_port']}";
            if(!in_array($local_host,$local_used_host_list)){
                $local_used_host_list[] = $local_host;
            }else{
                throw new \Exception("客户端转发地址配置项重复:{$local_host}");
            }

            $server_host = "{$c_value['server_port']}:{$c_value['server_port']}";
            if(!in_array($server_host,$server_used_host_list)){
                $server_used_host_list[] = $server_host;
            }else{
                throw new \Exception("服务端转发地址配置项重复:{$local_host}");
            }

            
        }
    }

    return $config;

}

/**
 * check_config
 * 检查配置完整性
 * @param mixed $config 
 * @return mixed 
 */
function check_config($config){

    //必选配置
    $tartget_key_array = [
        'local_ip'=>'',
        'local_port'=>'',
        'server_ip'=>'',
        'server_port'=>'',
    ];



    // 是否设置多个服务
    if(isset($config['nat_list'])){

        foreach ($config['nat_list'] as $c_key => $c_value) {
            
            // nat_list里没有设置但是应当设置的key
            $not_set_key_list = array_diff_key($tartget_key_array,$c_value);
            
            foreach ($not_set_key_list as $n_key => $n_value) {
                if(!isset($config[$n_key])){
                    // nat_list没有,一级配置也没有的设置,错误!
                    throw new \Exception("一级和二级配置缺少配置项:{$n_key}.只有一级配置完整,二级配置项才可以省略.");
                }else{
                    //把总设置赋给nat_list的设置项里
                    $config['nat_list'][$c_key][$n_key] = $config[$n_key];
                }

                if(!isset($config['nat_list'][$c_key]['name'])){
                    $config['nat_list'][$c_key]['name'] = $config['nat_list'][$c_key]['local_port']."<->".$config['nat_list'][$c_key]['server_port'];
                }
                if(!isset($config['nat_list'][$c_key]['password'])){
                    $config['nat_list'][$c_key]['password'] = "phpnb";
                }
                if(!isset($config['nat_list'][$c_key]['channel_port'])){
                    $config['nat_list'][$c_key]['channel_port'] = 2206;
                }

            }

            $char = '';
            $char .= $config['nat_list'][$c_key]['server_ip'];
            $char .= $config['nat_list'][$c_key]['server_port'];
            $char .= $config['nat_list'][$c_key]['local_ip'];
            $char .= $config['nat_list'][$c_key]['local_port'];
            $char .= $config['nat_list'][$c_key]['channel_port'];
            $char .= $config['nat_list'][$c_key]['name'];
            $char .= $config['nat_list'][$c_key]['password'];

            $config['nat_list'][$c_key]['channel_unique_key'] = md5($char);
        }
    }else{
        // 如果只是单个服务,那么一级配置一定要齐全
        foreach ($tartget_key_array as $k_key => $k_value) {
            if(!isset($config[$k_key])){
                throw new \Exception('未配置选项:'.$k_key.".windows下必须配齐所有一级配置.");
            }
        }
        if(!isset($config['name'])){
            $config['name'] = $config['local_port']."<->".$config['server_port'];
        }
        if(!isset($config['password'])){
            $config['password'] = "phpnb";
        }
        if(!isset($config['channel_port'])){
            $config['channel_port'] = "2206";
        }

        $char = '';
        $char .= $config['server_ip'];
        $char .= $config['server_port'];
        $char .= $config['local_ip'];
        $char .= $config['local_port'];
        $char .= $config['channel_port'];
        $char .= $config['name'];
        $char .= $config['password'];

        $config['channel_unique_key'] = md5($char);
    }

    return $config;

}

/**
 * 检查端口是否可以被绑定
 * @author flynetcn
 */
function check_port_bindable($host, $port, &$errno=null, &$errstr=null)
{
  $socket = stream_socket_server("tcp://$host:$port", $errno, $errstr);
  if (!$socket) {
    return false;
  }
  fclose($socket);
  unset($socket);
  return true;
}


function is_win()
{
    if (DIRECTORY_SEPARATOR === '\\') {
        return true;
    }else{
        return false;
    }
}

function debug_echo($content){
    global $is_debug;
    if($is_debug){
        echo $content;
    }
}

function build_client_woker($config){
    
    $inside_worker = new Worker();

    $inside_worker->inChannel = false;

    $inside_worker->onWorkerStart = function() use ($inside_worker,$config){

        // Channel客户端连接到Channel服务端
        Channel\Client::connect($config['server_ip'], $config['channel_port']);

        //注册客户端到分布式通讯服务


        // 每2.5秒执行一次
        $time_interval = 2.5;
        $inside_worker->start_register_timer = Timer::add($time_interval, function() use($config)
        {
            $register_data['channel_unique_key'] = $config['channel_unique_key'];
            
            Channel\Client::publish('cs_register',$register_data);
            // debug_echo("正在与服务器建立频道\n");
        });

        Channel\Client::on('sc_register_'.$config['channel_unique_key'],function($event_data) use ($inside_worker){
            if($event_data['status'] == 1){
                $inside_worker->inChannel = true;
                // debug_echo("与服务器建立频道成功\n");
                // Timer::del($inside_worker->start_register_timer);
                // debug_echo("清除登陆定时器\n");
            }
        });

        Channel\Client::on('cs_connect_'.$config['channel_unique_key'], function($event_data) use($inside_worker,$config){
            debug_echo("收到外网连接\n");
            $local_host_name = "tcp://".$config['local_ip'].":".$config['local_port'];
            
            $connection_to_local = new AsyncTcpConnection($local_host_name);

            $connection_to_local->onConnect = function($connection) use ($event_data,$config){
                debug_echo("与本地端口({$config['local_port']})连接成功\n");
                $connect_data['connection'] = [
                    'ip'=>$connection->getRemoteIp(),
                    'port'=>$connection->getRemotePort(),
                    'c_connection_id'=>$event_data['connection']['c_connection_id']
                ];
                Channel\Client::publish('sc_connect_'.$config['channel_unique_key'],$connect_data);
            };

            $connection_to_local->onMessage = function($connection,$data) use($config,$event_data){
                // $message_data['session'] = $_SESSION;
                $message_data['data'] = $data;
                $message_data['connection'] = [
                    'ip'=>$connection->getRemoteIp(),
                    'port'=>$connection->getRemotePort(),
                    'c_connection_id'=>$event_data['connection']['c_connection_id']
                ];

                Channel\Client::publish('sc_message_'.$config['channel_unique_key'],$message_data);
            };

            $connection_to_local->onClose = function($connection) use($event_data,$config){
                // $close_data['session'] = $_SESSION;
                $close_data['connection'] = [
                    'ip'=>$connection->getRemoteIp(),
                    'port'=>$connection->getRemotePort(),
                    'c_connection_id'=>$event_data['connection']['c_connection_id']
                ];
                
                Channel\Client::publish('sc_close_'.$config['channel_unique_key'],$close_data);
            };
            
            $connection_to_local->connect();

            debug_echo("与本地端口({$config['local_port']})发起连接\n");

            $inside_worker->connections[$event_data['connection']['c_connection_id']] = $connection_to_local;
            
        });

        Channel\Client::on('cs_message_'.$config['channel_unique_key'],function($event_data)use($inside_worker){
            $inside_worker->connections[$event_data['connection']['c_connection_id']]->send($event_data['data']);
        });
        Channel\Client::on('cs_close'.$config['channel_unique_key'],function($event_data)use($inside_worker){
            if(
                isset($inside_worker->connections[$event_data['connection']['c_connection_id']])
                &&
                $inside_worker->connections[$event_data['connection']['c_connection_id']]!=null
            ){
                $inside_worker->connections[$event_data['connection']['c_connection_id']]->close();
            }
        });
    };

}

function build_server_woker($config){

    
    $outside_worker = new Worker('tcp://0.0.0.0:'.$config['server_port']);

    $outside_worker->isClientOnline = false;
    

    $outside_worker->lastRegisterTime  = time();

    $outside_worker->onWorkerStart = function() use ($outside_worker,$config){
        // Channel客户端连接到Channel服务端
        Channel\Client::connect('127.0.0.1', $config['channel_port']);
        
        Timer::add(1,function()use($outside_worker){
            if(time() - $outside_worker->lastRegisterTime > 5){
                $outside_worker->isClientOnline = false;
                foreach ($outside_worker->connections as $c_key => $c_value) {
                    $c_value->close();
                }
            }
        });


        Channel\Client::on('cs_register',function($event_data) use ($config,$outside_worker){
            if($event_data['channel_unique_key'] == $config['channel_unique_key']){
                $outside_worker->isClientOnline = true;
                $outside_worker->lastRegisterTime = time();
                $register_data['status'] = 1;
                Channel\Client::publish('sc_register_'.$config['channel_unique_key'],$register_data);
            }
        });

        Channel\Client::on('sc_message_'.$config['channel_unique_key'],function($event_data) use ($outside_worker){
            if(isset($outside_worker->connections[$event_data['connection']['c_connection_id']])){
                $outside_worker->connections[$event_data['connection']['c_connection_id']]->send($event_data['data']);
            }
        });

        Channel\Client::on('sc_close_'.$config['channel_unique_key'],function($event_data) use ($outside_worker){
            if(isset($outside_worker->connections[$event_data['connection']['c_connection_id']])){
                $outside_worker->connections[$event_data['connection']['c_connection_id']]->close();
            }
        });

        Channel\Client::on('sc_connect_'.$config['channel_unique_key'],function($event_data) use($outside_worker){

        });

    };
    
    $outside_worker->onConnect = function($connection) use ($config,$outside_worker){
    
        debug_echo("收到外网端口({$config['server_port']})连接\n");

        if(!$outside_worker->isClientOnline){
            debug_echo("内网客户端未上线,断开连接\n");
            $connection->close();
        }

        // $connection_data['session'] = $_SESSION;
        $connection_data['connection'] = [
            'ip'=>$connection->getRemoteIp(),
            'port'=>$connection->getRemotePort(),
            'c_connection_id'=>$connection->id
        ];
    
    
        Channel\Client::publish('cs_connect_'.$config['channel_unique_key'], $connection_data);
        
        $connection->onMessage = function($connection, $data) use ($config){
            // $message_data['session'] = $_SESSION;
            $message_data['connection'] = [
                'ip'=>$connection->getRemoteIp(),
                'port'=>$connection->getRemotePort(),
                'c_connection_id'=>$connection->id
            ];
            $message_data['data'] = $data;
    
            Channel\Client::publish('cs_message_'.$config['channel_unique_key'], $message_data);
            
        };
        
        $connection->onClose = function ($connection) use ($config){
        
            // $close_data['session'] = $_SESSION;
            $close_data['connection'] = [
                'ip'=>$connection->getRemoteIp(),
                'port'=>$connection->getRemotePort(),
                'c_connection_id'=>$connection->id
            ];
        
            Channel\Client::publish('cs_close_'.$config['channel_unique_key'], $close_data);
        };
    };

}

